package org.glandais.profiler;

import java.io.File;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
import java.lang.management.ManagementFactory;
import java.util.Date;

import javax.management.MBeanServer;
import javax.management.ObjectName;

import org.glandais.profiler.beans.Config;
import org.glandais.profiler.beans.ConfigMBean;
import org.glandais.profiler.beans.TraceStack;

import com.thoughtworks.xstream.XStream;

public class Agent {

	private static final String INDENT = " - "; //$NON-NLS-1$
	private static final String PROFILER_CONFIG_VAR = "${profiler.config}"; //$NON-NLS-1$
	private static final String DOT = "."; //$NON-NLS-1$
	private static final String PROFILER_XML = "profiler.xml"; //$NON-NLS-1$
	private static final String USER_HOME = "user.home"; //$NON-NLS-1$
	private static final String PROFILER_CONFIG = "profiler.config"; //$NON-NLS-1$
	private static final String CONFIG = "config"; //$NON-NLS-1$
	private static final String STACK = "stack"; //$NON-NLS-1$
	private static final String OBJECT_NAME = "org.glandais.profiler:type=Config"; //$NON-NLS-1$
	public static ConfigMBean configBean;
	public static final XStream xstream = createXStream();

	static {
		try {
			loadConfig();
			MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
			ObjectName name = new ObjectName(OBJECT_NAME);
			mbs.registerMBean(configBean, name);
		} catch (Exception e) {
			throw new RuntimeException(Messages.getString("Agent.FAILED_INIT"), e); //$NON-NLS-1$
		}
	}

	public static void premain(String agentArgument, Instrumentation instrumentation) {
		startAgent(agentArgument, instrumentation);
	}

	public static final XStream createXStream() {
		XStream result = new XStream();
		result.omitField(TraceStack.class, STACK);
		result.alias(CONFIG, Config.class);
		return result;
	}

	private static final void loadConfig() throws Exception {
		File f = null;

		String systemConfig = System.getProperty(PROFILER_CONFIG);

		if (systemConfig != null) {
			File fsys = new File(systemConfig);
			f = fsys;
			if (!f.exists()) {
				System.out.println(Messages.getString("Agent.CREATE_CONFIG") + f.getCanonicalPath()); //$NON-NLS-1$
				Config.saveConfig(f);
			}
		} else {
			String fileName = PROFILER_XML;
			File f1 = new File(fileName);
			File f2 = new File(DOT, fileName);
			String userHome = System.getProperty(USER_HOME);
			File userHomeFile = new File(userHome);
			File f3 = new File(userHomeFile, fileName);
			if (f == null || !f.exists()) {
				f = f1;
				if (!f.exists()) {
					f = f2;
					if (!f2.exists()) {
						f = f3;
					}
				}
			}

			if (!f.exists()) {
				System.out.println(Messages.getString("Agent.NO_CONFIG")); //$NON-NLS-1$
				System.out.println(INDENT + PROFILER_CONFIG_VAR);
				System.out.println(INDENT + f1.getCanonicalPath());
				System.out.println(INDENT + f2.getCanonicalPath());
				System.out.println(INDENT + f3.getCanonicalPath());
				System.out.println(Messages.getString("Agent.CREATE_CONFIG") + f1.getCanonicalPath()); //$NON-NLS-1$
				Config.saveConfig(f1);
				f = f1;
			}
		}
		System.out.println(Messages.getString("Agent.CONFIG") + f.getCanonicalPath()); //$NON-NLS-1$
		configBean = Config.loadConfig(f);
	}

	public static void agentmain(String agentArgument, Instrumentation instrumentation) {
		startAgent(agentArgument, instrumentation);

		try {
			ClassFileTransformer classFileTransformer = new ClassFileTransformer(agentArgument);
			Class<?>[] allLoadedClasses = instrumentation.getAllLoadedClasses();
			for (Class<?> class1 : allLoadedClasses) {
				try {
					String className = class1.getName();
					if (classFileTransformer.shouldTransform(className)) {
						byte[] newBytes = classFileTransformer.loadAndTransform(class1.getClassLoader(), className,
								class1.getClass(), class1.getProtectionDomain());
						instrumentation.redefineClasses(new ClassDefinition(class1, newBytes));
					}
				} catch (Exception e) {
					configBean.logError(Messages.getString("Agent.FAILED_TO_INSTRUMENT") + class1.getName(), e); //$NON-NLS-1$
				}
			}
		} catch (Exception e) {
			configBean.logError(Messages.getString("Agent.FAILED_TO_INIT"), e); //$NON-NLS-1$
		}

	}

	private static final void startAgent(String agentArgument, Instrumentation instrumentation) {
		configBean.logVerbose(Messages.getString("Agent.PROFILING_START") + new Date()); //$NON-NLS-1$
		Runtime.getRuntime().addShutdownHook(new Thread() {
			public void run() {
				TraceRecorder.shutdown();
				configBean.logVerbose(Messages.getString("Agent.PROFILING_STOP") + new Date()); //$NON-NLS-1$
			}
		});
		try {
			instrumentation.addTransformer(new ClassFileTransformer(agentArgument));
		} catch (Exception e) {
			configBean.logError(Messages.getString("Agent.FAILED_TO_INIT"), e); //$NON-NLS-1$
		}
	}

}
